CPython函数级hook小技巧

一个pyinstller打包程序的函数级hook小技巧

原理

PyInstaller打包完的CPython运行环境中暴露了几个导出函数

  • PyGILState_Ensure
  • PyGILState_Release
  • PyRun_SimpleStringFlags
  • Py_IsInitialized

其中PyRun_SimpleStringFlags支持直接在目标程序上下文中直接运行py脚本,GIL是python解释器的互斥锁,只要当前线程拿到这个锁,就可以在解释器里执行任意的东西

整体流程为frida轮询Py_IsInitializedd确认cpython环境初始化完毕,然后调用PyGILState_Ensure把GIL关联到frida线程,用PyRun_SimpleStringFlags执行想执行的python代码后再通过PyGILState_Releases释放锁

实操

遇到一个神秘的租号软件,输入卡密后从远程服务器拉取cookie注入到定制chrome浏览器中

查看了这个定制浏览器发现把devtool和插件功能都屏蔽了,没法导出cookie到外面用
考虑到cookie注入的过程中肯定有一个阶段是明文cookie,于是让ai分析一下这个程序,居然是纯pyc实现

1
2
3
4
5
MainWindow._launch_browser_with_cookies
-> CookieExtractor.launch_browser(...)
-> CookieExtractor.create_context(storage_data=...)
-> context.add_cookies(storage_data["cookies"])
-> context.add_init_script(...) # localStorage

所以直接把app.cookie_extractor模块里的CookieExtractor.create_context给替换了,然后把cookie解析出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def _install_cookie_hooks():
module = _sys.modules.get("app.cookie_extractor")
cls = getattr(module, "CookieExtractor", None) if module is not None else None
if cls is None:
return

original = getattr(cls, "create_context", None)
if original is not None and not getattr(original, "__frida_audit_wrapped__", False):
async def create_context(self, storage_data):
_emit("cookie_injection", source="login_response_create_context",
storage=_storage_summary(storage_data))
return await original(self, storage_data)
create_context.__frida_audit_wrapped__ = True
cls.create_context = create_context
_emit("hook_installed", target="CookieExtractor.create_context")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
function findPythonApi() {
const modules = Process.enumerateModules();
for (const module of modules) {
const ensure = module.findExportByName('PyGILState_Ensure');
const release = module.findExportByName('PyGILState_Release');
const run = module.findExportByName('PyRun_SimpleStringFlags');
const initialized = module.findExportByName('Py_IsInitialized');
if (ensure && release && run && initialized) {
return { module, ensure, release, run, initialized };
}
}
return null;
}

let api = null;
let installed = false;
let attempts = 0;

function runPython(source) {
const gil = new NativeFunction(api.ensure, 'int', [])();
try {
const run = new NativeFunction(api.run, 'int', ['pointer', 'pointer']);
const code = Memory.allocUtf8String(source);
return run(code, ptr(0));
} finally {
new NativeFunction(api.release, 'void', ['int'])(gil);
}
}

const timer = setInterval(function () {
attempts += 1;
if (api === null) {
api = findPythonApi();
if (api !== null) {
console.log('[FRIDA-AUDIT] CPython found in ' + api.module.name);
}
}
if (api === null) {
return;
}
const isInitialized = new NativeFunction(api.initialized, 'int', [])();
if (!isInitialized) {
return;
}
const source = installed
? 'import builtins; builtins.__site_login_audit_install()'
: PYTHON_AUDIT;
const result = runPython(source);
if (result === 0) {
installed = true;
}
if (installed && attempts > 120) {
clearInterval(timer);
}
}, 100);

Author

SGSG

Posted on

2026-08-19

Updated on

2026-08-20

Licensed under